package com.sheng.project.security.webmvc.config;

import com.sheng.project.security.webmvc.property.SecurityApplicationProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.util.StopWatch;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Spring Security 核心配置类
 *
 * @author by ls
 * @date 2023/9/8
 */
@Slf4j
@Configuration
@EnableWebSecurity // 启用基于 web 的安全性配置
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用全局方法级别的安全性配置
@EnableConfigurationProperties(SecurityApplicationProperties.class) // 启用配置属性类的自动装配
@Profile({"web", "webmvc"}) // 指定在特定的环境或配置文件中才加载或运行的组件
@Import({SecurityProblemSupport.class}) // 将其他配置类或组件导入到当前配置类中
@Order // 用于指定组件的加载顺序或优先级
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String PATTERNS_ALL = "/**";

    protected final Environment env;

    private final SecurityProblemSupport securityProblemSupport;

    private final SecurityApplicationProperties properties;

    public SecurityConfig(Environment env, SecurityProblemSupport securityProblemSupport, SecurityApplicationProperties properties) {
        this.env = env;
        this.securityProblemSupport = securityProblemSupport;
        this.properties = properties;
    }

    /**
     * Security 的核心配置
     *
     * @param http Spring Security 提供的一个核心类，用于配置 Web 安全性相关的规则
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        log.info("HTTP 安全自动配置...");
        StopWatch watch = new StopWatch();
        watch.start();
        List<String> permit = new LinkedList<>();
        Map<String, List<String>> authenticateMap = new LinkedHashMap<>();
        List<String[]> authenticateList = new LinkedList<>();
        if (this.env.acceptsProfiles(Profiles.of("security"))) {
            this.properties.getMatchers().forEach((key, matcher) -> {
                permit.addAll(matcher.getPermit());
                authenticateList.addAll(matcher.getAuthenticate().
                        stream().
                        map(o -> o.split(":")).
                        collect(Collectors.toList()));
            });
            authenticateList.forEach(o -> {
                String path = o[0];
                String authority = o.length > 1 ? o[1] : "_def";
                if (authority != null && !authenticateMap.containsKey(authority)) {
                    authenticateMap.put(authority, new LinkedList<>());
                }
                authenticateMap.get(authority).add(path);
            });
            if (this.properties.getModes().contains("resource")) {
                // OAuth2 资源服务器配置
                http.oauth2ResourceServer().jwt();
            }
            if (this.properties.getModes().contains("client")) {
                // OAuth2 客户端配置
                http.oauth2Client()
                        // OAuth2 单点登录配置
                        .and().oauth2Login(Customizer.withDefaults());
            }
        } else {
            permit.add(PATTERNS_ALL);
        }
        final ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        authenticateMap.forEach((k, v) -> {
            if ("_def".equals(k)) {
                registry.antMatchers(v.toArray(new String[]{})).authenticated();
            } else {
                registry.antMatchers(v.toArray(new String[]{})).hasAuthority(k);
            }
        });
        registry
                // 允许
                .antMatchers(HttpMethod.OPTIONS, PATTERNS_ALL).permitAll()
                // 允许
                .antMatchers(permit.toArray(new String[]{})).permitAll()
                // 鉴权
                .anyRequest().authenticated()
                // 异常处理设置
                .and().exceptionHandling()
                // 403 异常处理
                .accessDeniedHandler(this.securityProblemSupport)
                // 401 异常处理
                .authenticationEntryPoint(this.securityProblemSupport)
                // 头部信息设置 - 允许 iframe
                .and().headers().frameOptions().disable()
                // 禁用 CSRF
                .and().csrf().disable();
        log.info("HTTP 安全自动配置完成 {} ms", watch.getTotalTimeMillis());
    }
}
